#include "uri.h"
#include "re.h"
#include "str.h"
#include <stdexcept>


namespace evm {

Uri::Uri(const string& uri) {
    static const auto uri_regex = re::compile(
        "([a-zA-Z][a-zA-Z0-9+.-]*):"  // scheme:
        "([^?#]*)"                    // authority and path
        "(?:\\?([^#]*))?"             // ?query
        "(?:#(.*))?"                  // #fragment
    );

    static const auto authority_path_regex = re::compile("//([^/]*)(/.*)?");

    auto uri_match = uri_regex.match(uri);
    if (!uri_match)
        throw invalid_argument("invalid uri: " + uri);

    scheme = str::lower(uri_match.group(1));

    auto authority_path = uri_match.group(2);

    auto authority_path_match = authority_path_regex.match(authority_path);
    if (!authority_path_match) {
        has_authority = false;
        path = authority_path;
    } else {
        static const auto authority_regex = re::compile(
            "(?:([^@:]*)(?::([^@]*))?@)?"  // username, password
            "(\\[[^\\]]*\\]|[^\\[:]*)"     // host (IP-literal (e.g. '['+IPv6+']',
                                           // dotted-IPv4, or named host)
            "(?::(\\d*))?"                 // port
        );
        auto authority = authority_path_match.group(1);
        auto authority_match = authority_regex.match(authority);
        if (!authority_match)
            throw invalid_argument("invalid uri authority: " + authority);

        auto port = authority_match.group(4);
        if (!port.empty())
            this->port = static_cast<uint16_t>(std::stoul(port));

        has_authority = true;
        username = authority_match.group(1);
        password = authority_match.group(2);
        host = authority_match.group(3);

        path = authority_path_match.group(2);
    }

    query = uri_match.group(3);
    fragment = uri_match.group(4);
}

string Uri::authority() const {
    string res;
    res.reserve(host.size() + username.size() + password.size() + 8);

    if (!username.empty() || !password.empty()) {
        res.append(username);

        if (!password.empty()) {
            res.push_back(':');
            res.append(password);
        }

        res.push_back('@');
    }

    res.append(host);

    if (port != 0) {
        res.push_back(':');
        res.append(std::to_string(port));
    }

    return res;
}

string Uri::str() const {
    string res;

    if (has_authority) {
        res += scheme + "://";
        if (!password.empty())
            res += username + ":" + password + "@";
        else if (!username.empty())
            res += username + "@";
        res += host;
        if (port != 0)
            res += ":" + std::to_string(port);
    } else
        res += scheme + ":";

    res += path;

    if (!query.empty())
        res += "?" + query;

    if (!fragment.empty())
        res += "#" + fragment;

    return res;
}

const QueryParams& Uri::query_params() {
    if (!query.empty() && query_params_.empty()) {
        static const auto query_params_regex = re::compile(
            "(^|&)"      /*start of query or start of parameter "&"*/
            "([^=&]*)=?" /*parameter name and "=" if value is expected*/
            "([^=&]*)"   /*parameter value*/
            "(?=(&|$))"  /*forward reference, next should be end of query or
                           start of next parameter*/
        );

        auto query_params_matches = query_params_regex.search_all(query);
        for (auto& match: query_params_matches) {
            if (match.group(2).size() == 0)
                continue;  // key is empty, ignore it
            query_params_.emplace_back(match.group(2), match.group(3));
        }
    }

    return query_params_;
}

}
